package com.vision.sales.advancepayment.cl.ui;

import static com.vision.sales.cl.SalesServices.advancePaymentSvc;
import static com.vision.sales.cl.SalesServices.companySvc;
import static com.vision.sales.cl.SalesServices.customerSvc;

import java.util.ArrayList;
import java.util.List;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.data.client.loader.RpcProxy;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.Store;
import com.sencha.gxt.data.shared.loader.LoadEvent;
import com.sencha.gxt.data.shared.loader.LoadResultListStoreBinding;
import com.sencha.gxt.data.shared.loader.PagingLoadConfig;
import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoader;
import com.sencha.gxt.widget.core.client.TabItemConfig;
import com.sencha.gxt.widget.core.client.Window;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
import com.sencha.gxt.widget.core.client.event.HideEvent;
import com.sencha.gxt.widget.core.client.event.ParseErrorEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.DateField;
import com.sencha.gxt.widget.core.client.form.NumberField;
import com.sencha.gxt.widget.core.client.form.NumberPropertyEditor;
import com.sencha.gxt.widget.core.client.form.StoreFilterField;
import com.sencha.gxt.widget.core.client.form.TextArea;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.form.validator.MaxLengthValidator;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.info.Info;
import com.sencha.gxt.widget.core.client.menu.Item;
import com.sencha.gxt.widget.core.client.menu.MenuItem;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler;
import com.sencha.gxt.widget.core.client.toolbar.PagingToolBar;
import com.vision.core.cl.BaseModule;
import com.vision.core.cl.Callback;
import com.vision.core.cl.event.CreatedEvent;
import com.vision.core.cl.event.CreatedEvent.CreationHandler;
import com.vision.core.cl.ui.ConfirmDialogHandler;
import com.vision.core.cl.ui.Notifications;
import com.vision.core.cl.ui.TabItemPanel;
import com.vision.core.cl.ui.UI;
import com.vision.core.cl.ui.component.BasicGridView;
import com.vision.core.cl.ui.component.BasicPagingLoader;
import com.vision.core.cl.ui.component.CompanyComboBox;
import com.vision.core.cm.db.data.Company;
import com.vision.sales.cl.SalesServices;
import com.vision.sales.cl.SalesTasks;
import com.vision.sales.cl.data.AdvancePaymentProperties;
import com.vision.sales.cl.data.AppliedAdvancePaymentProperties;
import com.vision.sales.cl.event.AdvancePaymentCreatedEvent;
import com.vision.sales.cl.event.AppliedAdvancePaymentCreatedEvent;
import com.vision.sales.cl.resource.SalesResources;
import com.vision.sales.cl.ui.component.CustomerComboBox;
import com.vision.sales.cl.ui.component.TransactionLoadConfig;
import com.vision.sales.cl.ui.component.TransactionNoCell;
import com.vision.sales.cm.db.data.AdvancePayment;
import com.vision.sales.cm.db.data.AdvancePayment.FieldSpecs;
import com.vision.sales.cm.db.data.AppliedAdvancePayment;
import com.vision.sales.cm.db.data.Customer;

/**
 * 
 * @author Mark
 *
 */
public class AdvancePaymentsPanel extends TabItemPanel implements Editor<AdvancePayment> {

	interface Binder extends UiBinder<Widget, AdvancePaymentsPanel> { }

	private static final Binder binder = GWT.create(Binder.class);
	
	@UiField VerticalLayoutContainer mainPanel;
	
	// Ignored by the form driver since these are not fields in the AdvancePayment
	@Ignore @UiField StoreFilterField<AdvancePayment> gridFilter;
	
	@Ignore @UiField TextButton addBtn;
	@Ignore @UiField TextButton applyBtn;
	@Ignore @UiField TextButton moreBtn;
		@Ignore @UiField MenuItem refreshMenuItem;
		@Ignore @UiField MenuItem deleteMenuItem;

	@Ignore @UiField TextButton postBtn;
	@Ignore @UiField TextButton deleteAppBtn;
	@UiField Grid<AdvancePayment> grid;
	@UiField PagingToolBar pagingToolbar;
	PagingLoader<PagingLoadConfig, PagingLoadResult<AdvancePayment>> loader;
	
	// Form fields that are bound by the form driver to the fields in the AdvancePayment class.
	// The name of these form fields must match the name of the field in the AdvancePayment class.
	@UiField CompanyComboBox company;
	@UiField CustomerComboBox customer;
	@UiField TextField advancePaymentNo;
	@UiField DateField date;
	@UiField NumberField<Double> amount;
	@Ignore @UiField NumberField<Double> balance;
	@UiField TextArea remarks;
	
	// Ignored by the form driver since these are not fields in the AdvancePayment
	@Ignore @UiField TextButton saveBtn;
	@Ignore @UiField TextButton cancelBtn;
	
	@UiField(provided = true) NumberFormat numberFormat = UI.amountFormat();
	@UiField(provided = true) NumberPropertyEditor<Double> doublePropertyEditor = UI.doubleEditor();
	
	// Interface used to bind the form fields to the AdvancePayment fields
	interface AdvancePaymentDriver extends SimpleBeanEditorDriver<AdvancePayment, AdvancePaymentsPanel> { }
	
	// Instance of the AdvancePaymentDriver interface
	private AdvancePaymentDriver driver = GWT.create(AdvancePaymentDriver.class);
	
	@UiField(provided = true) Grid<AppliedAdvancePayment> appliedAdvancePaymentsGrid;
 
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	public AdvancePaymentsPanel() {
		// Global Handlers -- should be registered only once
		BaseModule.addHandler(AdvancePaymentCreatedEvent.TYPE, new CreationHandler<AdvancePayment>() {
			@Override
			public void onCreated(CreatedEvent<AdvancePayment> e) {
				grid.getStore().add(e.getObject());
			}
		});

		BaseModule.addHandler(AppliedAdvancePaymentCreatedEvent.TYPE, new CreationHandler<AppliedAdvancePayment>() {
			@Override
			public void onCreated(CreatedEvent<AppliedAdvancePayment> e) {
				AppliedAdvancePayment payment = e.getObject();
				
				appliedAdvancePaymentsGrid.getStore().add(payment);
				grid.getStore().update(payment.getAdvancePayment());
			}
		});
		
		appliedAdvancePaymentsGrid = initializeAppliedAdvancePaymentsGrid();
		
		setWidget(binder.createAndBindUi(this));
		
		// Bind the filter into the grid's store
		gridFilter.bind(grid.getStore());
		
		date.setPropertyEditor(UI.datePropertyEditor());
		
		// Initialize loaders
		RpcProxy<PagingLoadConfig, PagingLoadResult<AdvancePayment>> proxy = new RpcProxy<PagingLoadConfig, PagingLoadResult<AdvancePayment>>() {
		    @Override
		    public void load(PagingLoadConfig loadConfig, AsyncCallback<PagingLoadResult<AdvancePayment>> callback) {
		    	TransactionLoadConfig tConfig = new TransactionLoadConfig(loadConfig);
		    	SalesServices.advancePaymentSvc().getList(tConfig, callback);
		    }
	    };
	    
	    loader = new BasicPagingLoader<AdvancePayment>(proxy, grid.getStore());
	    loader.addLoadHandler(new LoadResultListStoreBinding<PagingLoadConfig, AdvancePayment, PagingLoadResult<AdvancePayment>>(grid.getStore()) {
        	@Override
        	public void onLoad(LoadEvent<PagingLoadConfig, PagingLoadResult<AdvancePayment>> event) {
        		super.onLoad(event);
        		grid.getSelectionModel().deselectAll();
        		edit(null);
        	}
        });
        
        grid.setLoader(loader);
        grid.setLoadMask(true);
		
		pagingToolbar.bind(loader);
		
		// Initialize the driver, used to run the form editor
		driver.initialize(this);
		date.setPropertyEditor(UI.datePropertyEditor());
		
		advancePaymentNo.addValidator(new MaxLengthValidator(FieldSpecs.AP_NO_MAX));
		remarks.addValidator(new MaxLengthValidator(FieldSpecs.REMARKS_MAX));
	}
		
	// UiFactory Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

	@Ignore
	@UiFactory
	StoreFilterField<AdvancePayment> initializeGridFilter() {
		StoreFilterField<AdvancePayment> f = new StoreFilterField<AdvancePayment>() {
			@Override
			protected boolean doSelect(Store<AdvancePayment> store, AdvancePayment parent, AdvancePayment item, String filter) {
				String filterInLowerCase = filter.toLowerCase().trim();
				return item.getAdvancePaymentNo().toLowerCase().contains(filterInLowerCase);
			}
		};
		return f;
	}
	
	/**
	 * Used to create the 'grid' field in the XML file. 
	 * 
	 * @return
	 */
	@UiFactory
	Grid<AdvancePayment> initializeGrid() {
		AdvancePaymentProperties props = AdvancePaymentProperties.INSTANCE;
		
		List<ColumnConfig<AdvancePayment, ?>> columns = new ArrayList<ColumnConfig<AdvancePayment, ?>>();
		ListStore<AdvancePayment> store = new ListStore<AdvancePayment>(props.key());

		columns.add(UI.column(props.date(), 50, "Date", UI.dateCell()));
		columns.add(UI.column(props.companyCode(), 50, "Company"));
		columns.add(UI.column(props.advancePaymentNo(), 70, "A/P No.")); 
		ColumnConfig<AdvancePayment, String> column = UI.column(props.customerCode(), 100, "Customer");
		columns.add(column);
		columns.add(UI.column(props.amount(), 80, "Amount", UI.decimalCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		columns.add(UI.column(props.balance(), 80, "Balance", UI.decimalCell(), HasHorizontalAlignment.ALIGN_RIGHT));
		
		ColumnModel<AdvancePayment> cm = new ColumnModel<AdvancePayment>(columns);

		final Grid<AdvancePayment> grid = new Grid<AdvancePayment>(store, cm);
		grid.setAllowTextSelection(false);
		grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
		
		GridView<AdvancePayment> view = new BasicGridView<AdvancePayment>();
		view.setEmptyText("There are no advance payments to display.");
		view.setAutoExpandColumn(column);
		grid.setView(view);
		
		// Add a listener to the grid
		grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<AdvancePayment>() {
			@Override
			public void onSelectionChanged(SelectionChangedEvent<AdvancePayment> event) {
				onGridSelectionChanged(event);
			}
		});
		
		return grid;
	}
	
	Grid<AppliedAdvancePayment> initializeAppliedAdvancePaymentsGrid() {
		AppliedAdvancePaymentProperties props = AppliedAdvancePaymentProperties.INSTANCE;
		
		List<ColumnConfig<AppliedAdvancePayment, ?>> columns = new ArrayList<ColumnConfig<AppliedAdvancePayment, ?>>();
		ListStore<AppliedAdvancePayment> store = new ListStore<AppliedAdvancePayment>(props.key());
		
		columns.add(UI.column(props.salesInvoiceNo(), 80, "Invoice No.", new TransactionNoCell<AppliedAdvancePayment>(store)));
		columns.add(UI.column(props.salesInvoiceDate(), 80, "Invoice Date", UI.dateCell()));
		columns.add(UI.column(props.appliedAmount(), 80, "Amount", UI.amountCell()));
		
		ColumnModel<AppliedAdvancePayment> cm = new ColumnModel<AppliedAdvancePayment>(columns);
		
		final Grid<AppliedAdvancePayment> grid = new Grid<AppliedAdvancePayment>(store, cm);
		grid.setAllowTextSelection(false);
		grid.getSelectionModel().setSelectionMode(SelectionMode.MULTI);
		
		GridView<AppliedAdvancePayment> view = new BasicGridView<AppliedAdvancePayment>();
		view.setEmptyText("There are no applied advance payments to display.");
//		view.setAutoExpandColumn(column);
		grid.setView(view);
		
		// Add a listener to the grid
		grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<AppliedAdvancePayment>() {
			@Override
			public void onSelectionChanged(SelectionChangedEvent<AppliedAdvancePayment> event) {
				// TODO Auto-generated method stub
			}
		});
		
		return grid;
	}
	
	// Logic %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	void onGridSelectionChanged(SelectionChangedEvent<AdvancePayment> event) {
		final AdvancePayment advancePayment = grid.getSelectionModel().getSelectedItem();
		if (advancePayment == null)
			return;
		
		if (driver.isDirty()) { // If there are unsaved changes in the form
			UI.confirm("Unsaved Changes", "There are unsaved changes. Would you like to continue loading?",
				new ConfirmDialogHandler() {
					@Override
					public void onYes(HideEvent event) {
						edit(advancePayment);
					}
					
					public void onCancel(HideEvent event) {
						// just close the message box
					}
					
					public void onNo(HideEvent event) {
						// previously selected item should remain selected 
					}
				}
			);
		} else {
			edit(advancePayment);
		}
	}
	
	/**
	 * Loads <tt>advancePayment</tt> into the form editor.
	 * 
	 * @param advancePayment
	 */
	private void edit(final AdvancePayment advancePayment) {
		driver.edit(advancePayment);
		
		saveBtn.setEnabled(advancePayment != null);
		cancelBtn.setEnabled(advancePayment != null);
		
		loadAppliedPayments(advancePayment);
	}

	private void loadAppliedPayments(final AdvancePayment advancePayment) {
		appliedAdvancePaymentsGrid.getStore().clear();
		
		if (advancePayment == null)
			return;

		UI.mask(appliedAdvancePaymentsGrid, "Loading Applied Advance Payments...");
		SalesServices.appliedAdvancePaymentSvc().getAppliedAdvancePaymentsForAdvancePayment(advancePayment.getId(),
			new Callback<List<AppliedAdvancePayment>>(appliedAdvancePaymentsGrid) {
				protected void handleSuccess(List<AppliedAdvancePayment> result) {
					appliedAdvancePaymentsGrid.getStore().addAll(result);
				}
			});
	}		
	
	// Listeners %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	// Called when the 'Add' button is clicked
	@UiHandler("addBtn")
	void addClicked(SelectEvent e) {
		Window w = (Window) new CreateAdvancePaymentWindow().asWidget();
		w.show();
	}
	
	@UiHandler("applyBtn")
	void applyClicked(SelectEvent e) {
		AdvancePayment advancePayment = grid.getSelectionModel().getSelectedItem();
		if (advancePayment == null) {
			Notifications.info("Please select at least one advance payment.");
			return;
		}

		if (advancePayment.getBalance() <= 0d) {
			UI.info("No More Balance", "The selected advance payment has already been used up.", null);
			return;
		}
		
		ApplyAdvancePaymentWindow aapw = new ApplyAdvancePaymentWindow(advancePayment);
		Window w = (Window) aapw.asWidget();
		w.show();
	}
	
	@UiHandler("moreBtnMenu")
	void onMenuSelection(SelectionEvent<Item> e) {
		Item item = e.getSelectedItem();
		if (item == refreshMenuItem) {
			refreshClicked();
		} else if (item == deleteMenuItem) {
			deleteClicked();
		}
	}

	@UiHandler("postBtn")
	void postClicked(SelectEvent e) {
		List<AppliedAdvancePayment> selected = appliedAdvancePaymentsGrid.getSelectionModel().getSelectedItems();
		if (selected.isEmpty()) {
			Notifications.info("Please select at least one advance payment.");
			return;
		}
		
		appliedAdvancePaymentsGrid.mask("Posting advance payments...");
		SalesServices.appliedAdvancePaymentSvc().postAppliedAdvancePayments(selected, 
				new Callback<List<AppliedAdvancePayment>>(appliedAdvancePaymentsGrid) {
				@Override
				protected void handleSuccess(List<AppliedAdvancePayment> result) {
					for (AppliedAdvancePayment payment : result)
						appliedAdvancePaymentsGrid.getStore().update(payment);
				}
			});
	}
	
	@UiHandler("deleteAppBtn")
	void deleteAppClicked(SelectEvent e) {
		List<AppliedAdvancePayment> selected = appliedAdvancePaymentsGrid.getSelectionModel().getSelectedItems();
		if (selected.isEmpty()) {
			Notifications.info("Please select at least one advance payment.");
			return;
		}
		
		appliedAdvancePaymentsGrid.mask("Deleting advance payments...");
		SalesServices.appliedAdvancePaymentSvc().delete(selected, 
				new Callback<List<AppliedAdvancePayment>>(appliedAdvancePaymentsGrid) {
				@Override
				protected void handleSuccess(List<AppliedAdvancePayment> result) {
					for (AppliedAdvancePayment payment : result)
					{
						appliedAdvancePaymentsGrid.getStore().remove(payment);
						grid.getStore().update(payment.getAdvancePayment());
					}
				}
			});
	}
	
	// Called when the 'Refresh' button is clicked
	void refreshClicked() {
		loadData();
	}
	
	// Called when the 'Delete' button is clicked
	void deleteClicked() {
		final AdvancePayment selected = grid.getSelectionModel().getSelectedItem();
		if (selected == null)
			return;
		
		UI.confirm("Confirm Delete", "Are you sure you want to delete '" + selected.getAdvancePaymentNo() + "'?",
			new ConfirmDialogHandler() {
				@Override
				public void onYes(HideEvent event) {
					UI.mask(mainPanel, "Deleting " + selected.getAdvancePaymentNo() + "...");
					advancePaymentSvc().delete(selected, new Callback<AdvancePayment>(mainPanel) {
						@Override
						protected void handleSuccess(AdvancePayment result) {
							grid.getStore().remove(result);
							
							// Clear the form editor if necessary
							AdvancePayment inEditor = driver.flush();
							if (inEditor != null && inEditor.getAdvancePaymentNo().equals(result.getAdvancePaymentNo()))
								edit(null);
						}
					});
				}
			}
		);
	}
	
	// Called when the 'Save' button is clicked
	@UiHandler("saveBtn")
	void saveClicked(SelectEvent e) {
		AdvancePayment advancePayment = driver.flush();
		if (driver.hasErrors())
			return;
		
		UI.mask(mainPanel, "Updating advance payment...");
		advancePaymentSvc().update(advancePayment, new Callback<AdvancePayment>(mainPanel) {
			@Override
			protected void handleSuccess(AdvancePayment result) {
				grid.getStore().update(result);
				edit(result);
			}
		});
	}
	
	// Called when the 'Cancel' button is clicked
	@UiHandler("cancelBtn")
	void cancelClicked(SelectEvent e) {
		edit(null);
	}
	 
	// ITabItemPanel Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Override
	public String getPanelId() {
		return SalesTasks.ADVANCE_PAYMENTS.getId();
	}
	
	@Override
	public TabItemConfig getConfig() {
		if (config == null) {
			config = new TabItemConfig("Advance Payments", true);
			config.setIcon(SalesResources.INSTANCE.advancePayments());
		}
		return config;
	}
	
	@Override
	public void loadData() {
		Log.debug("Loading companies, customers, sales invoices, for dropdown...");

		UI.mask(company, "Loading company...");
		companySvc().getNameAndIdOnly(new Callback<List<Company>>(company) {
			@Override
			protected void handleSuccess(List<Company> result) {
				company.getStore().clear();
				company.getStore().addAll(result);
			}
		});

		UI.mask(customer, "Loading customers...");
		customerSvc().getCustomerNameAndIdOnly(new Callback<List<Customer>>(customer) {
			@Override
			protected void handleSuccess(List<Customer> result) {
				customer.getStore().clear();
				customer.getStore().addAll(result);
			}
		});
		
		// Load data
		Log.debug("Loading Advance Payemnts...");
		loader.load();
		edit(null);
	}
	
	@UiHandler("date")
	public void onDateParseError(ParseErrorEvent event) {
		Info.display("Parse Error", event.getErrorValue() + " could not be parsed as a date");
	}
	
}
